﻿#PowerShell Monitoring Script
# vam at celenia.com

<#History
Version 1.2
New features:
* Event log - new service to test. Check Application and System event logs for errors.
* LogFile - Now you may found more info in log file 
* Twitter - Send notification to Twitter. New parameters: -generateTweet and -tweetDMUsers  Use MyTwitter module http://www.adamtheautomator.com/twitter-module-powershell/ 
* EventLogMSSQL - Like EventLog, but dedicated only for MSSQL* providers
Fixes:
* SQL - Fixed second SQL script to check that every DB is online
* HTML - added URL to email report to open Dashboard. New parameter: -dashboardURL

Version 1.1
New features:
* Generate JSON report. 
   Added new parameters -generateJson $true or $false and -generateJsonFile Path to JSON file
* Generate HTML report/Dashboard. 
   Added new parameters -generateHTML $true or $false and -generateHTMLFile Path to HTML file. 
   Place this file inside IIS web site. E.g., put file C:\inetpub\wwwroot\HealthReport.html, Then access http://your_site.com/healthreport.html 
#>

param
(
    [Parameter(Mandatory=$false, HelpMessage="PowerShell Monitoring Script Configuration file")]
    [string]$CSVFile = 'C:\Scripts\ServerList.csv', #Config file

    [Parameter(Mandatory=$false, HelpMessage="PowerShell Monitoring Script History file")]
    [string]$CSVFileHistory = 'C:\Scripts\ServerOutageHistory.csv', #History file. If not exist, then script create new one

    [Parameter(Mandatory=$false, HelpMessage="PowerShell Monitoring Script Log file")]
    [string]$logFile = 'C:\Scripts\Get-Status-LogFile.txt', #Log file

    [Parameter(Mandatory=$false, HelpMessage="Send email as report. Each time кegardless of changes in the history.")]
    [boolean]$mailReport = $false, #send email in anyway. Send email as report
    [string[]]$emailTo = "you@yourdomain.com", # or $emailTo = ("you@yourdomain.com","yourfriend@yourdomain.com"),
    [string]$emailfrom = "Monitor@yourdomain.com", 
    [string]$emailSmtpServer = "smtp.yourdomain.com",

    [Parameter(Mandatory=$false, HelpMessage="JSON: Generate report in JSON format")]
    [boolean]$generateJson = $true, #Write info to JSON file
    [Parameter(Mandatory=$false, HelpMessage="JSON: path to file in JSON format")]
    [String]$generateJsonFile = 'C:\Scripts\Get-Status_Report.json', #JSON file

    [Parameter(Mandatory=$false, HelpMessage="HTML: Generate report in HTML format")]
    [boolean]$generateHTML = $true, #Write info to HTML file
    [Parameter(Mandatory=$false, HelpMessage="HTML: path to file in HTML format")]
    [String]$generateHTMLFile = 'C:\inetpub\wwwroot\HealthReport.html', #HTML file

    [Parameter(Mandatory=$false, HelpMessage="Twiter: Generate notification in tweeter account")]
    [boolean]$generateTweet = $true,  #Send twitter. Please make sure that you have installed and configured MyTwitter module http://www.adamtheautomator.com/twitter-module-powershell/ 
    [Parameter(Mandatory=$false, HelpMessage="Twiter: The list of twiter acount to send Direct Message")]
    [string[]]$tweetDMUsers = ("YourTweeter"), #If $tweetDMUsers not empty, then Send Direct Message (DM) to the listed tweeter users

    [Parameter(Mandatory=$false, HelpMessage="Dashboard: URL to include to every report")]
    [string]$dashboardURL = "http://www.yourdomain.com:53245/healthreport.html"
)



#region variables
$StatusError = 'Error'
$StatusWarning = 'Warning'
$StatusOK = 'OK'
$StatusTesting = 'Testing' #When test is not finished yet...
$StatusWriteLog = 'Info' #Just write Description to log file
$dt = Get-Date #Get current date time
$mailStatusChanged = $false #don't touch this

$CSSstyle = @"
<style>

table{
	font-family: verdana,arial,sans-serif;
	font-size:11px;
	color:#333333;
	border-width: 1px;
	border-color: #666666;
	border-collapse: collapse;
	width: 100%
}
table th {
	border-width: 1px;
	padding: 8px;
	border-style: solid;
	border-color: #666666;
	background-color: #dedede;
}
table td {
	border-width: 1px;
	padding: 8px;
	border-style: solid;
	border-color: #666666;
	background-color: #ffffff;
}
</style>
"@
#endregion 

#region Function LogMessage
Function LogMessage
{
	[CmdletBinding()]
	Param(
		[Parameter(Mandatory=$true)] [string]$Message,
		[switch]$AppendTime = $true,
		[switch]$NoNewLine = $false
	)
	
	if ($AppendTime)
	{
		$Message = ("[{0:HH:mm:ss}] $Message" -f (Get-Date))
	}
	Write-Host $message	-BackgroundColor DarkGray -ForegroundColor Cyan -NoNewline:$NoNewLine
	Add-Content $logFile $message
}
#endregion

#region Function LogMessageStatus
Function LogMessageStatus
{
	[CmdletBinding()]
	Param(
		[Parameter(Mandatory=$true)] [string]$Status,
		[string]$ComputerName = '',
		[string]$Description = ''
	)
	
	switch($Status)
	{
		$StatusOK {
			$message = "...testing "+ $ComputerName +" .. ["+$Status+"]"
			Write-Host "..."             -ForegroundColor Cyan -NoNewline
			Write-Host ('['+$Status+']') -ForegroundColor Green
			Add-Content $logFile $message
		}
		$StatusWarning {
			$message = "...testing "+ $ComputerName +" .. ["+$Status+"]"
			Write-Host "..."             -ForegroundColor Cyan -NoNewline
			Write-Host ('['+$Status+']') -ForegroundColor Yellow
			Add-Content $logFile $message
			if($Description) { Add-Content $logFile $Description }
		}
		$StatusError {
			$message = "...testing "+ $ComputerName +" .. ["+$Status+"]"
			Write-Host "..."             -ForegroundColor Cyan -NoNewline
			Write-Host ('['+$Status+']') -ForegroundColor Red
			Add-Content $logFile $message
			if($Description) { Add-Content $logFile $Description }
		}
		$StatusTesting { 
			$message = "...testing "+$ComputerName 
			Write-Host $message	-ForegroundColor Cyan -NoNewline
		}
		$StatusWriteLog {
			$message = "...info regarding "+ $ComputerName +" .. ["+$Status+"]"
			Write-Host "..."             -ForegroundColor Cyan -NoNewline
			Write-Host ('['+$Status+']') -ForegroundColor Green
			Add-Content $logFile $message
			if($Description) { Add-Content $logFile $Description }
		}
		default {
			$message = "...Unknown status "+ $Status +" for "+$ComputerName 
			Write-Host $message	-BackgroundColor DarkGray -ForegroundColor DarkRed 
			Add-Content $logFile $message
		}
	}
}
#endregion

#region Function LogRotate
Function LogRotate
{
    [CmdletBinding()]
    Param(
	[Parameter(Mandatory=$true)] [string]$logfile,
        [double]$RotationSizeMb = 1,
        [string]$FileExtensionOld = '.old'
    )
    $file = Get-Item -Path $logfile
    if($file)
    {
        if( $file.Length/1Mb -ge $RotationSizeMb )
        {
	        Write-Host "Log rotation for: " $logfile -BackgroundColor DarkGray -ForegroundColor Cyan
            $FileExtensionCurrent = $file.Extension
            $oldLogFile = $logFile.Replace($FileExtensionCurrent,$FileExtensionOld)
            if( Test-Path -Path $oldLogFile )
            {
                Remove-Item -Path $oldLogFile -Confirm:$false
            }
            $file | Rename-Item -NewName $oldLogFile
        }
    }
}
#endregion

#region Function Update-HTMLtags
Function Update-HTMLtags
{
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [Object[]]$InputObject
    )
    
    process {
        [Object[]]$out = $null;
        foreach($line in $InputObject)
        {
            Switch -regex ($Line)
            {
                "<td>$StatusError</td>" {
                    #$line = $line.Replace("<tr>","<tr style=""color: yellow; font-size: 120%; font-weight: bold; background-color:darkred;"">")
                    $line = $line.Replace("<td>$StatusError</td>","<td style=""color:yellow; font-size:120%; font-weight:bold; background-color:darkred;"">$StatusError</td>")
                    #Break
                }
                "<td>$StatusWarning</td>" {
                    #$line = $line.Replace("<tr>","<tr style=""background-color:GOLD;"">")
                    $line = $line.Replace("<td>$StatusWarning</td>","<td style=""color=black; font-size:120%; font-weight:bold; background-color:#FFD700;"">$StatusWarning</td>")
                    #Break
                }
                "<td>" {   #$Line = $Line.Replace("<tr>","<tr style=""background-color:red"">")
                    $Line = $Line.Replace("`n","<br />")
                    #Break
                }

            }
            $out += $line
        }
        return $out
    }

}
#endregion

#region Function trap
#trap
function trapError
{
	LogMessage $_.Exception -appendTime

	$subject = "Get-Status script failed at $dt"
	$body = "<html>$CSSstyle<body>"
	$body += "<h1>Get-Status script failed</h1>"
	$body += ("<p>Time: {0:G}</>" -f (Get-Date))
	$body += '<p>Error: ' + $_.Exception.Message + '</p>'
	$body += '<p>Script: {0} (line {1})</p>' -f $_.InvocationInfo.ScriptName, $_.InvocationInfo.ScriptLineNumber
	$body += '<p>Environment: Command line: {0}</p>' -f [environment]::CommandLine
	$body += '<p>Environment: Computer name: {0}</p>' -f [environment]::MachineName
	$body += $config | ConvertTo-HTML -Fragment 
	$body += "</html></body>"

	Send-MailMessage -to $emailTo -from $emailfrom -SmtpServer $emailSmtpServer -subject $subject -BodyAsHtml "$body"
	$body
	Write-Error "Error"
	#exit
}
#endregion

#region Read config file and extend it
try
{
    LogRotate -logfile $logFile
    LogMessage $("***** Running ***** " + $(Get-Date -Format G))
    $config = Import-Csv -Path $CSVFile -Delimiter ';' -ErrorAction Stop
    $config | Add-Member -MemberType NoteProperty -Name Status -Value 'Not tested'
    $config | Add-Member -MemberType NoteProperty -Name Description -Value ''
}
catch
{
    trapError
}
#endregion

#region Common server tests (Ping, avgCPU, RAM usage, freeSpaceDisk)
LogMessage "***** Common Server tests *****"
foreach($server in $($config | where{$_.Service -eq 'Server'}))
{
    $computername = $server.Server
    LogMessageStatus -ComputerName $computername -Status $StatusTesting
    try
    {
        $testConnection = Test-Connection -ComputerName $computername -Quiet -Count 2 -ErrorAction SilentlyContinue

        if ($testConnection)
        {
            $server.Status = $StatusOK

            $AVGProc = Get-WmiObject -computername $computername win32_processor |  `
            Measure-Object -property LoadPercentage -Average | Select Average 

            $OS = gwmi -Class win32_operatingsystem -computername $computername | `
            Select-Object @{Name = "MemoryUsage"; Expression = {“{0:N2}” -f ((($_.TotalVisibleMemorySize - $_.FreePhysicalMemory)*100)/ $_.TotalVisibleMemorySize) }} 

            #$server.Description += "ping successfully $([System.Net.Dns]::GetHostEntry($computername).HostName)"
            $server.Description += "avgCPU=$($AVGProc.Average)% usageRAM=$($OS.MemoryUsage)%"


            foreach($logicaldisk in $(Get-WmiObject win32_logicaldisk -ComputerName $computername -Filter "Drivetype=3"))
            {
                #Write-Host $logicaldisk
                $logicaldisk_freespacepercent = (($logicaldisk.FreeSpace / $logicaldisk.Size) * 100)
                $logicaldisk_freespaceGB  = [math]::Round($logicaldisk.FreeSpace / 1024 / 1024 / 1024,0)
                $logicaldisk_totalspaceGB = [math]::Round($logicaldisk.Size / 1024 / 1024 / 1024,0)
            
                $server.Description += "`n $($logicaldisk.Name) ($($logicaldisk.VolumeName)) "+$logicaldisk_totalspaceGB+"GB total, "+$logicaldisk_freespaceGB +"GB free, "+[math]::Round($logicaldisk_freespacepercent,1)+ "% free"

                #Set Service status
                if ( `
                    ([double]$logicaldisk_freespacepercent -le [double]20.0) `
                    -and ($server.Status -ne $StatusError) `
                    -and ($logicaldisk.VolumeName -notlike "DoNotUse*")`
                    )
                {
                    $server.Status = $StatusWarning
                    if ([double]$logicaldisk_freespacepercent -le [double]7.0) 
                    {
                        $server.Status = $StatusError
                    }
    
                }
            }
        }
        else
        {
            $server.Status = $StatusError
            $server.Description += "Can't ping server $computername DNS resolve:$([System.Net.Dns]::GetHostEntry($computername).HostName)"
        }
    }
    catch
    {
        $server.Status = $StatusError
        #$server.Description = "Can't ping it"
    }
    LogMessageStatus -ComputerName $computername -Status $server.Status -Description $server.Description
}
#endregion

#region Common SQL tests 
LogMessage "***** Common SQL tests *****"
foreach($server in $($config | where{$_.Service -eq 'SQL'}))
{
    $computername = $server.Server
    LogMessageStatus -ComputerName $computername -Status $StatusTesting

    $server.Status = $StatusError
    ##Create a string variable with all our connection details 
    $database = "master" 
    $connectionDetails = "Provider=sqloledb; " + "Data Source=$computername; " + "Initial Catalog=$database; " + "Integrated Security=SSPI;"

    $sql_server_info = @"
select CAST( SERVERPROPERTY('IsClustered') AS NVARCHAR(128)) AS [IsClustered],
CAST( SERVERPROPERTY('ComputerNamePhysicalNetBIOS')AS NVARCHAR(128)) AS [SQLService_Current_Node],
@@version as [Serverversion]
"@
    ##Connect to the data source using the connection details and T-SQL command we provided above, and open the connection
    $connection = New-Object System.Data.OleDb.OleDbConnection $connectionDetails
    $command1 = New-Object System.Data.OleDb.OleDbCommand $sql_server_info,$connection
    $connection.Open()
    ##Get the results of our command into a DataSet object, and close the connection
    $dataAdapter = New-Object System.Data.OleDb.OleDbDataAdapter $command1
    $dataSet1 = New-Object System.Data.DataSet
    $dataAdapter.Fill($dataSet1)
    $connection.Close()
    $table1 = $dataSet1.Tables | Select-Object -Expand rows
    if($table1)
    {
        $server.Description = "Active node:" +$table1.SQLService_Current_Node +"`n" +$table1.Serverversion
        $server.Status = $StatusOK
    }
    else
    {
        $server.Status = $StatusError
    }

$sql_database_info = @"
SELECT NAME,STATE_DESC,user_access_desc FROM SYS.DATABASES 
WHERE STATE_DESC<>'ONLINE'
OPTION (RECOMPILE);
"@

    if($server.Status -eq $StatusOK) #Run database info
    {
        $command2 = New-Object System.Data.OleDb.OleDbCommand $sql_database_info,$connection
        $connection.Open()
        ##Get the results of our command into a DataSet object, and close the connection
        $dataAdapter = New-Object System.Data.OleDb.OleDbDataAdapter $command2
        $dataSet2 = New-Object System.Data.DataSet
        $dataAdapter.Fill($dataSet2)
        $connection.Close()
        $table2 = $dataSet2.Tables | Select-Object -Expand rows
        if($table2)
        {
            $Server.Status = $StatusError
            foreach($row in $table2)
            {
                $server.Description += "`n" +$row.NAME + " " +$row.STATE_DESC + " " +$row.user_access_desc
            }
        }
        else
        {
            $server.Description += "All databases are online"
        }
    }
    LogMessageStatus -ComputerName $computername -Status $server.Status -Description $server.Description
}
#endregion

#region Windows Cluster tests
LogMessage "***** Windows Cluster tests *****"
foreach($server in $($config | where{$_.Service -eq 'Cluster'}))
{
    $computername = $server.Server
    LogMessageStatus -ComputerName $computername -Status $StatusTesting

    #local version   $cluster = Get-CLuster -Name $computername
    $cluster = Invoke-Command -ComputerName $computername {Get-Cluster|Get-ClusterNode}
    if ($cluster)
    {
        $server.Status = $StatusOK
        $server.Description += "Cluster $computername DNS resolve:$([System.Net.Dns]::GetHostEntry($computername).HostName)"

        <#local version
        $clusterNodes = $cluster | Get-ClusterNode 
        $clusterRoles = $cluster | Get-ClusterGroup  
        #>
        $clusterNodes = Invoke-Command -ComputerName $computername {Get-Cluster|Get-ClusterNode}
        $clusterRoles = Invoke-Command -ComputerName $computername {Get-Cluster|Get-ClusterGroup} 

        if($clusterNodes | where {$_.State -ne 'Up'})
        {
            $server.Status = $StatusWarning
            foreach($node in ($clusterNodes | where {$_.State -ne 'Up'}))
            {
                $server.Description += "`n Node "+$node.Name +" "+ $node.State
            }

        }

        foreach($role in $clusterRoles)
        {
            $server.Description += "`n Role:"+$role.Name+" Active node="+$role.OwnerNode+" State="+$role.State
            if($role.State -ne 'Online') 
            {
                $server.Status = $StatusError
            }
        }
    }
    else
    {
        $server.Status = $StatusError
        $server.Description += "Can't find cluster $computername DNS resolve:$([System.Net.Dns]::GetHostEntry($computername).HostName)"
    }
    LogMessageStatus -ComputerName $computername -Status $server.Status -Description $server.Description
}
#endregion

#region StarWind tests
LogMessage "***** StarWind tests *****"
<#no remote version. You should install StarWindX dll and StarWindX PowerShell module 
regsvr32.exe C:\Scripts\StarWindX\x64\StarWindX.dll
#>
$p = [Environment]::GetEnvironmentVariable("PSModulePath")
##$p += ";C:\Scripts\StarWindX\Modules"
$p += ";C:\Scripts\Modules"
[Environment]::SetEnvironmentVariable("PSModulePath",$p)
Import-Module StarWindX

foreach($server in $($config | where{$_.Service -eq 'StarWind'}))
{
    $computername = $server.Server
    LogMessageStatus -ComputerName $computername -Status $StatusTesting

    $starWind = New-SWServer -host $computername -user root -password starwind -port 3261
    $starWind.Connect()
    if ( $starWind.Connected )
    { 
        foreach($device in ($starWind.Devices | where {$_.deviceType -eq 'HA Image'}))
        {
            #$device
            $syncState = $device.GetPropertyValue("ha_synch_status")
            $syncPercent = $device.GetPropertyValue("ha_synch_percent")
            switch($syncState)
            {
                1 {$deviceState = 'Synchronized'                   ; $server.Status = $StatusOK }
                2 {$deviceState = 'Synchronizing'                  ; $server.Status = $StatusWarning }
                3 {$deviceState = 'Not synchronized'               ; $server.Status = $StatusError }
                default {$deviceState = "Unknown state $syncState" ; $server.Status = $StatusError }
            }
            #Write-Host $syncPercent -ForegroundColor Yellow
            if($server.Description -ne '') { $server.Description += "`n" }
            $server.Description += "" +$device.TargetName + " is "+ $deviceState + " ("+$syncPercent+"%)"
            
        }
    
        $starWind.Disconnect()
    }
    else
    {
        $server.Status = $StatusError
        $server.Description += "No StarWind service found"
    }
    LogMessageStatus -ComputerName $computername -Status $server.Status -Description $server.Description
}
#endregion
#region Azure tests

#region Website tests
LogMessage "***** Website tests *****"
foreach($server in $($config | where{$_.Service -eq 'Website'}))
{
    $uri = $server.Server
    LogMessageStatus -ComputerName $uri -Status $StatusTesting

    if($uri)
    {
        $time = try{ 
            $webrequest = $null 
            ## Request the URI, and measure how long the response took. 
            $webresult1 = Measure-Command { $webrequest = Invoke-WebRequest -Uri $uri } 
            $webresult1.TotalMilliseconds 
        }  
        catch 
        { 
            <# If the request generated an exception (i.e.: 500 server error or 404 not found), 
            we can pull the status code from the Exception.Response property #> 
            $webrequest = $_.Exception.Response 
            $time = -1 
        }  
        $server.Description += "StatusCode="+ [int] $webrequest.StatusCode +" ("+$webrequest.StatusDescription `
                                +"), ResponseLength="+$webrequest.RawContentLength + ", TimeTaken="+$time+"ms"
        if($webrequest.StatusCode -ne "200") 
        {
            $server.Status = $StatusError
        }
        else
        {
            $server.Status = $StatusOK
        }
    }
    LogMessageStatus -ComputerName $computername -Status $server.Status -Description $server.Description
}
#endregion

#region AX Application (AOS) Service tests
LogMessage "***** AX Application (AOS) Service tests *****"
foreach($server in $($config | where{$_.Service -eq 'AOS'}))
{
    $computername = $server.Server
    LogMessageStatus -ComputerName $computername -Status $StatusTesting

    $AOSservices = Get-Service -Name "AOS*" -ComputerName $computername 
    if($AOSservices)
    {
        $server.Status = $StatusOK
        foreach($service in $AOSservices)
        {
            if($server.Description -ne '') { $server.Description += "`n" }
            $server.Description += ""+$service.Status +" Name:" +$service.Name +" Description:=" +$service.DisplayName
            
            if($service.Status -ne 'Running')
            {
                $server.Status = $StatusError
            }
        }
    }
    else
    {
        $server.Status = $StatusWarning
        $server.Description += "No AOS service installed at $computername (DNS resolve:$([System.Net.Dns]::GetHostEntry($computername).HostName)) or $computername is not available. "
    }
    LogMessageStatus -ComputerName $computername -Status $server.Status -Description $server.Description
}
#endregion

#region EventLog
LogMessage "***** Check Event log for any errors *****"
$afterdt = ($dt | Get-Date).AddMinutes(-10);
if( Test-Path -Path $CSVFileHistory ) { $afterdt = (Get-Item -Path $CSVFileHistory).LastWriteTime } #Get last date time of history event.
foreach($server in $($config | where{$_.Service -eq 'EventLog'}))
{
    $computername = $server.Server
    LogMessageStatus -ComputerName $computername -Status $StatusTesting

	$server.Status = $StatusOK
    #$eventLog = Get-EventLog -LogName System -EntryType Error -ComputerName $computername -After $afterdt| where {($_.EventID -ne 10028) -and ($_.EventID -ne 4)}
    #$eventLog = Get-EventLog -LogName Application -EntryType Error -ComputerName $computername -After $afterdt

    #That will work starting with Windows Server 2008 R2
	$eventLog = Get-Winevent -ComputerName $computername -FilterHashtable @{LogName="System","Application"; Level=1,2; startTime=$afterdt;} -ErrorAction SilentlyContinue `
                | Where {($_.ID -ne 10028) -and ($_.ID -ne 4)}

    foreach($log in $eventLog) 
	{
		$server.Status = $StatusError
		if($server.Description -ne '') { $server.Description += "`n" }
		$server.Description += ""+$log.logName+ ":" +$log.ProviderName +" EventId=" +$log.Id+" "+ $log.Message
	}

	if( ($server.Status -eq $StatusOK) -and ($server.Description -eq '') ) 
	{
		$server.Description += 'Application and System Logs has no errors after '+$afterdt
	}
    LogMessageStatus -ComputerName $computername -Status $server.Status -Description $server.Description
}
#endregion

#region EventLogMSSQL
LogMessage "***** Check Event log for MSSQL *****"
$afterdt = ($dt | Get-Date).AddMinutes(-10);
if( Test-Path -Path $CSVFileHistory ) { $afterdt = (Get-Item -Path $CSVFileHistory).LastWriteTime; $afterdt = ($afterdt).AddMinutes(-3); } #Get last date time of history event.
foreach($server in $($config | where{$_.Service -eq 'EventLogMSSQL'}))
{
    $computername = $server.Server
    LogMessageStatus -ComputerName $computername -Status $StatusTesting

	$server.Status = $StatusOK
	$eventLog = Get-WinEvent -ComputerName $computername -FilterHashtable @{LogName="Application"; Level=1,2; startTime=$afterdt; ProviderName="MSSQL*"} -ErrorAction SilentlyContinue

    foreach($log in $eventLog) 
	{
		$server.Status = $StatusError
		if($server.Description -ne '') { $server.Description += "`n" }
		$server.Description += ""+ $log.Message
	}

	if( ($server.Status -eq $StatusOK) -and ($server.Description -eq '') ) 
	{
		$server.Description += 'MSSQL Event Logs has no errors between '+$afterdt+' and '+$dt
	}
    LogMessageStatus -ComputerName $computername -Status $server.Status -Description $server.Description
}
#endregion

#region Keep outage history
#it should be the last step, right before send email notification
LogMessage "***** Checking the history *****"
$history = Import-Csv -Path $CSVFileHistory -Delimiter ';'
if (!$history)
{
    $history = $config|select Server, Service, Status
}
if (!$($history | Get-Member -Name LastKnownStatus)) { $history | Add-Member -Name LastKnownStatus -MemberType NoteProperty -Value $dt }
$StatusChangedTxt = '';
$tweetMessage = '';

foreach($c in $config)
{
    $h = $history | where {($_.Server -eq $c.Server) -and ($_.Service -eq $c.Service)}
    if(!$h)
    {   #add new item to history
        $h = ( $c|select Server,Service,Status )
        if (!$($h | Get-Member -Name LastKnownStatus)) { $h | Add-Member -Name LastKnownStatus -MemberType NoteProperty -Value $dt }
        $history += $h
    }
    #check the latest status
    if($c.Status -ne $h.Status)#  -and ($c.Status -ne $StatusError)) #if current status is not Error  and status has been changed
    {
        $mailStatusChanged = $true;
        if($StatusChangedTxt) {$StatusChangedTxt += "<br />`n"; $tweetMessage += "|"} #CRLF
        $StatusChangedTxt += "Status "+$c.Server+" ("+$c.Service+") changed from "+$h.Status+" to <b>"+$c.Status +"</b> at "+ ($dt|Get-Date -Format G)
        $tweetMessage += ""+$c.Server+" "+$c.Service+" "+$h.Status+"->"+$c.Status 
        if(($c.Status -ne $StatusError) -and ($h.Status -ne $StatusOK)) #if current status != Error  OR History status != OK
        {
            $StatusChangedTxt += " Outage was "+[math]::Round((New-TimeSpan -Start $h.LastKnownStatus -End $dt).TotalMinutes,0) +" minutes."
            $tweetMessage += " for "+[math]::Round((New-TimeSpan -Start $h.LastKnownStatus -End $dt).TotalMinutes,0) +" m."
        }
        $h.LastKnownStatus = $dt
        $h.Status = $c.Status
    }
}
LogMessage $("..Should I send email? " +$mailStatusChanged)
#$history | ft -AutoSize
$history | Export-Csv -Path $CSVFileHistory -Delimiter ';' 
#endregion

#region Tweeter 
if( $generateTweet -and $mailStatusChanged )
{
    LogMessage "***** Sending tweet *****"
    LogMessageStatus -Status $StatusTesting -ComputerName $env:COMPUTERNAME
    Import-Module MyTwitter

    #ReWrite Send-TwitterDM function 
    function Send-TwitterDm {
	<#
	.SYNOPSIS
		This sends a DM to another Twitter user.  NOTE: You can only send up to 
		250 DMs in a 24 hour period.
	.EXAMPLE
		Send-TwitterDm -Message 'hello, Adam' -Username 'adam','bill'
	
		This sends a DM with the text 'hello, Adam' to the username 'adam' and 'bill'
	.PARAMETER Message
		The text of the DM.
	.PARAMETER Username
		The username(s) you'd like to send the DM to.
	#>
	[CmdletBinding()]
	[OutputType('System.Management.Automation.PSCustomObject')]
	param (
		[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
		[ValidateLength(1, 140)]
		[string]$Message,
		[Parameter(Mandatory)]
		[string[]]$Username
	)
	
	process {
		$HttpEndPoint = 'https://api.twitter.com/1.1/direct_messages/new.json'
	
		$AuthorizationString = Get-OAuthAuthorization -Api 'DirectMessage' -ApiParameters @{ 'screen_name' = $Username; 'text' = $Message } -HttpEndPoint $HttpEndPoint -HttpVerb 'POST'
		
		## Convert the message to a Byte array
		#vam $Message = [System.Uri]::EscapeDataString($Message)
		foreach ($User in $Username) {
			Write-Verbose "Using text=$Message" #vam debug
            $User = [System.Uri]::EscapeDataString($User)
			$Body = [System.Text.Encoding]::ASCII.GetBytes("text=$Message&screen_name=$User");
			Write-Verbose "Using POST body '$Body'"
			Invoke-RestMethod -URI $HttpEndPoint -Method Post -Body $Body -Headers @{ 'Authorization' = $AuthorizationString } -ContentType "application/x-www-form-urlencoded"
		}
		
	 }
    }


    if ((Get-MyTwitterConfiguration) -and ($tweetMessage -ne ''))
    {
        $tinyURL = Get-ShortURL -URL $dashboardURL -provider isgd
        if($tinyURL) { $tweetMessage = $tinyURL + " " +$tweetMessage }
        if($tweetMessage.Length -ge 138) {$tweetMessage = $tweetMessage.Substring(0,138)} 
        $tweetMessage = $tweetMessage.Replace('`n',''); #Remove CRLF
        $tweetResult = Send-Tweet -Message $tweetMessage -Verbose

        #Send DM tweets NOTE: You can only send up to 250 DMs in a 24 hour period.
        if($tweetDMUsers) { Send-TwitterDm -Message $tweetMessage -Username $tweetDMUsers -Verbose }
        LogMessageStatus -Status $StatusWriteLog -ComputerName $env:COMPUTERNAME -Description $tweetMessage
    }
    else
    {
        LogMessageStatus -Status $StatusError -ComputerName $env:COMPUTERNAME -Description "PowerShell MyTwitter Module is not configured. Please follow the guide http://www.adamtheautomator.com/twitter-module-powershell/"
    }
}
#endregion

#region Generate HTML Report
try
{
    LogMessage "***** Generate HTML Report *****"
    $subject = "PMS Health Report"
    $pre = ''
    if($tweetMessage) 
    {
        $pre += "<p><b>MSG: </b>$tweetMessage</p>"
    }
    $pre += "<h1>$subject</h1>"
    $pre += "<p>$($dt | Get-date -Format F)</p>"
    if($dashboardURL) { $pre += "<p><a href=""$dashboardURL"">Please find dashboard here anytime</a></p>" }
    if($mailStatusChanged)
    {
        $pre += "<h2>Changes</h2>"
        $pre += "<p style=""background-color:azure"" >" +$StatusChangedTxt+ "</p>"
    }
    $errors = $config|where{$_.Status -eq $StatusError}
    if($errors) #if any errors then
    {
        $pre += "<h1>Errors</h1>"
        $pre += $errors | ConvertTo-HTML -Fragment | Update-HTMLtags
    }
    $warnings = $config|where{$_.Status -eq $StatusWarning}
    if($warnings) #if any warnings then
    {
        $pre += "<h2>Warnings</h2>"
        $pre += $warnings | ConvertTo-HTML -Fragment | Update-HTMLtags
    }
    $pre += "<h3>Full list</h3>"
    $pre += "<p>$(Get-Date -Format F)</p>"
    $post  = $config | ConvertTo-HTML -Fragment | Update-HTMLtags
    
    $ts = New-TimeSpan -Start $dt
    $message = "<p>Report generated for $([math]::Round($ts.TotalSeconds,2)) seconds.</p>"
    if ($ts.TotalMinutes -ge 3) {$message = "<p>Report generated for $([math]::Round($ts.TotalMinutes,2)) minutes.</p>"}
    if ($ts.TotalHours -ge 2) {$message = "<p>Report generated for $([math]::Round($ts.TotalHours,2)) hours.</p>"}
    $post += $message


    $body = ConvertTo-HTML -Title $subject -PreContent $pre -PostContent $post -Head $CSSstyle
}
catch
{
    trapError
}
#endregion

#region Sending email
if($mailReport -or $mailStatusChanged)
{
    LogMessage "***** Sending email *****"
    Send-MailMessage -to $emailTo -from $emailfrom -SmtpServer $emailSmtpServer -subject $subject -BodyAsHtml "$body"
}
#endregion

#region generate HTML and JSON files
if( $generateJson )
{
    LogMessage "***** Generate JSON ***** "
    $json = $config | ConvertTo-Json | Out-File -FilePath $generateJsonFile
}
if( $generateHTML )
{
    LogMessage "***** Generate HTML ***** "
    if( $body )
    {
        $body | Out-File -FilePath $generateHTMLFile
    }
}
#endregion

LogMessage $("***** Finish ***** " + $(Get-Date -Format G))
#$config | Out-GridView
